﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration.Provider;
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using MVCQuick.Framework.Repository;
using MVCQuick.Framework.Container;

namespace MVCQuick.Framework.Security
{
    /// <summary>
    /// 管理ASP.NET 应用程序的成员资格信息存储。
    /// </summary>
    public class ClassicMembershipProvider : System.Web.Security.MembershipProvider
    {
        private string applicationName;
        private bool enablePasswordReset;
        private bool enablePasswordRetrieval;
        private int maxInvalidPasswordAttempts;
        private int minRequiredNonAlphanumericCharacters;
        private int minRequiredPasswordLength;
        private int passwordAttemptWindow;
        private System.Web.Security.MembershipPasswordFormat passwordFormat;
        private string passwordStrengthRegularExpression;
        private bool requiresQuestionAndAnswer;
        private bool requiresUniqueEmail;
        IRepository repository;
  
        private string s_HashAlgorithm;

        /// <summary>
        /// 获取或设置要存储和检索其成员资格信息的应用程序的名称。
        /// </summary>
        public override string ApplicationName
        {
            get
            {
                return applicationName;
            }
            set
            {
                if (String.IsNullOrEmpty(value))
                {
                    throw new ArgumentNullException("Provider application name not null.");
                }
                if (value.Length > 255)
                {
                    throw new ProviderException("Provider application name too long.");
                }
                applicationName = value;
            }
        }

        /// <summary>
        /// 获取一个值，指示成员资格提供程序是否配置为允许用户重置其密码。
        /// </summary>
        public override bool EnablePasswordReset
        {
            get { return enablePasswordReset; }
        }

        /// <summary>
        /// 获取一个值，指示成员资格提供程序是否配置为允许用户检索其密码。
        /// </summary>
        public override bool EnablePasswordRetrieval
        {
            get { return enablePasswordRetrieval; }
        }

        /// <summary>
        /// 获取锁定成员资格用户前允许的无效密码或无效密码提示问题答案尝试次数。
        /// </summary>
        public override int MaxInvalidPasswordAttempts
        {
            get { return maxInvalidPasswordAttempts; }
        }

        /// <summary>
        /// 获取有效密码中必须包含的最少特殊字符数。
        /// </summary>
        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return minRequiredNonAlphanumericCharacters; }
        }

        /// <summary>
        /// 获取密码所要求的最小长度。
        /// </summary>
        public override int MinRequiredPasswordLength
        {
            get { return minRequiredPasswordLength; }
        }

        /// <summary>
        /// 获取时间长度，在该时间间隔内对提供有效密码或密码答案的连续失败尝试次数进行跟踪。
        /// </summary>
        public override int PasswordAttemptWindow
        {
            get { return passwordAttemptWindow; }
        }

        /// <summary>
        /// 获取一个值，表示用于在成员资格数据库中存储密码的格式。
        /// </summary>
        public override System.Web.Security.MembershipPasswordFormat PasswordFormat
        {
            get { return passwordFormat; }
        }

        /// <summary>
        /// 获取用于计算密码的正则表达式。 
        /// </summary>
        public override string PasswordStrengthRegularExpression
        {
            get { return passwordStrengthRegularExpression; }
        }

        /// <summary>
        /// 获取一个值，指示成员资格提供程序是否配置为要求用户在进行密码重置和检索时回答密码提示问题。 
        /// </summary>
        public override bool RequiresQuestionAndAnswer
        {
            get { return requiresQuestionAndAnswer; }
        }

        /// <summary>
        /// 获取一个值，指示成员资格提供程序是否配置为要求每个用户名具有唯一的电子邮件地址。 
        /// </summary>
        public override bool RequiresUniqueEmail
        {
            get { return requiresUniqueEmail; }
        }


        /// <summary>
        /// 使用 ASP.NET 应用程序配置文件中指定的属性值初始化成员资格提供程序。
        /// 此方法不应从代码直接使用。
        /// </summary>
        /// <param name="name">要初始化的 SqlMembershipProvider 实例的名称。</param>
        /// <param name="config">
        /// 一个 NameValueCollection，其中包含成员资格提供程序配置选项的值和名称。 
        /// </param>
        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
            {
                throw new ArgumentNullException("config");
            }

            if (String.IsNullOrEmpty(name))
            {
                name = "ClassicMembershipProvider";
            }

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "MVCQuick.Framework.Security Membership Provider");
            }

            base.Initialize(name, config);

            this.enablePasswordRetrieval = SecUtility.GetBooleanValue(
                config, "enablePasswordRetrieval", false);
            this.enablePasswordReset = SecUtility.GetBooleanValue(
                config, "enablePasswordReset", true);
            this.requiresQuestionAndAnswer = SecUtility.GetBooleanValue(
                config, "requiresQuestionAndAnswer", true);
            this.requiresUniqueEmail = SecUtility.GetBooleanValue(
                config, "requiresUniqueEmail", true);
            this.maxInvalidPasswordAttempts = SecUtility.GetIntValue(
                config, "maxInvalidPasswordAttempts", 5, false, 0);
            this.passwordAttemptWindow = SecUtility.GetIntValue(
                config, "passwordAttemptWindow", 10, false, 0);
            this.minRequiredPasswordLength = SecUtility.GetIntValue(
                config, "minRequiredPasswordLength", 7, false, 128);
            this.minRequiredNonAlphanumericCharacters = SecUtility.GetIntValue(
                config, "minRequiredNonalphanumericCharacters", 1, true, 128);

            this.passwordStrengthRegularExpression = 
                config["passwordStrengthRegularExpression"];
            if (this.passwordStrengthRegularExpression != null)
            {
                this.passwordStrengthRegularExpression = 
                    this.passwordStrengthRegularExpression.Trim();
                if (this.passwordStrengthRegularExpression.Length != 0)
                {
                    try
                    {
                        new Regex(this.passwordStrengthRegularExpression);
                    }
                    catch (ArgumentException e)
                    {
                        throw new ProviderException(e.Message, e);
                    }
                }
            }
            else
            {
                this.passwordStrengthRegularExpression = String.Empty;
            }

            this.applicationName = config["applicationName"];
            if (String.IsNullOrEmpty(this.applicationName))
            {
                this.applicationName = SecUtility.GetDefaultAppName();
            }

            if (this.applicationName.Length > 255)
            {
                throw new ProviderException(
                    "Provider application name is too long, max length is 255.");
            }

            string strRepository = config["repository"];
            if (strRepository != null)
            {
                repository = ClassicContainer.GetObject(strRepository) as IRepository;
            }
            else
            {
                repository = ClassicContainer.GetObject<IRepository>() as IRepository;
            }

            if (!new ApplicationService(repository).ApplicationExists(this.applicationName))
            {
                new ApplicationService(repository).CreateApplication(this.applicationName);
            }

            string strTemp = config["passwordFormat"];
            if (strTemp == null)
            {
                strTemp = "Hashed";
            }

            switch (strTemp)
            {
                case "Clear":
                    this.passwordFormat = 
                        System.Web.Security.MembershipPasswordFormat.Clear;
                    break;
                case "Encrypted":
                    this.passwordFormat = 
                        System.Web.Security.MembershipPasswordFormat.Encrypted;
                    break;
                case "Hashed":
                    this.passwordFormat = 
                        System.Web.Security.MembershipPasswordFormat.Hashed;
                    break;
                default:
                    throw new ProviderException("Bad password format.");
            }

            if (this.passwordFormat == System.Web.Security.MembershipPasswordFormat.Hashed 
                && this.enablePasswordRetrieval)
            {
                throw new ProviderException("Provider cannot retrieve hashed password.");
            }
           
            config.Remove("repository");
            config.Remove("applicationName");            
            config.Remove("enablePasswordRetrieval");
            config.Remove("enablePasswordReset");
            config.Remove("requiresQuestionAndAnswer");
            config.Remove("requiresUniqueEmail");
            config.Remove("maxInvalidPasswordAttempts");
            config.Remove("passwordAttemptWindow");
            config.Remove("passwordFormat");
            config.Remove("name");
            config.Remove("description");
            config.Remove("minRequiredPasswordLength");
            config.Remove("minRequiredNonalphanumericCharacters");
            config.Remove("passwordStrengthRegularExpression");            

            if (config.Count > 0)
            {
                string attribUnrecognized = config.GetKey(0);
                if (!String.IsNullOrEmpty(attribUnrecognized))
                {
                    throw new ProviderException(
                        "Provider unrecognized attribute: " + attribUnrecognized);
                }
            }
        }

        /// <summary>
        /// 修改用户密码。
        /// </summary>
        /// <param name="username">为其更新密码的用户。</param>
        /// <param name="oldPassword">指定的用户的当前密码。</param>
        /// <param name="newPassword">指定的用户的新密码。</param>
        /// <returns></returns>
        public override bool ChangePassword(
            string username,
            string oldPassword, 
            string newPassword)
        {
            SecUtility.CheckParameter(ref username, true, true, true, 255, "username");
            SecUtility.CheckParameter(ref oldPassword, true, true, false, 128, "oldPassword");
            SecUtility.CheckParameter(ref newPassword, true, true, false, 128, "newPassword");

            string salt;
            System.Web.Security.MembershipPasswordFormat passwordFormat;

            if (!CheckPassword(username, oldPassword, false, false, 
                out salt, out passwordFormat))
            {
                return false;
            }

            if (newPassword.Length < this.minRequiredPasswordLength)
            {
                throw new ArgumentException("New Password too short.");
            }

            int nonAlphanumericCharacters = 0;

            for (int i = 0; i < newPassword.Length; i++)
            {
                if (!char.IsLetterOrDigit(newPassword, i))
                {
                    nonAlphanumericCharacters++;
                }
            }

            if (nonAlphanumericCharacters < this.minRequiredNonAlphanumericCharacters)
            {
                throw new ArgumentException("Password need more non alpha numeric chars.");
            }

            if ((this.passwordStrengthRegularExpression.Length > 0) 
                && !Regex.IsMatch(newPassword, this.passwordStrengthRegularExpression))
            {
                throw new ArgumentException("Password does not match regular expression.");
            }

            string pass = EncodePassword(newPassword, passwordFormat, salt);

            if (pass.Length > 128)
            {
                throw new ArgumentException("Membership password too long.");
            }

            System.Web.Security.ValidatePasswordEventArgs args = 
                new System.Web.Security.ValidatePasswordEventArgs(
                    username, newPassword, false);
            this.OnValidatingPassword(args);
            if (args.Cancel)
            {
                if (args.FailureInformation != null)
                {
                    throw args.FailureInformation;
                }
                throw new ArgumentException(
                    "Membership custom password validation failure.");
            }

            int status = new UserService(repository).UpdatePassword(
                this.applicationName, 
                username, 
                pass, 
                salt, 
                passwordFormat,
                DateTime.UtcNow);

            if (status != 0)
            {
                string errText = GetExceptionText(status);

                if (IsStatusDueToBadPassword(status))
                {
                    throw new System.Web.Security.MembershipPasswordException(errText);
                }
                throw new ProviderException(errText);
            }

            return true;
        }

        /// <summary>
        /// 更新成员资格数据库中用户的密码提示问题和答案。
        /// </summary>
        /// <param name="username">要为其更改密码提示问题和答案的用户。</param>
        /// <param name="password">指定的用户的密码。</param>
        /// <param name="newPasswordQuestion">指定的用户的新密码提示问题。</param>
        /// <param name="newPasswordAnswer">指定的用户的新密码提示问题答案。</param>
        /// <returns>
        /// 如果更新成功，则为 true；否则为 false。 
        /// 如果 password 错误、用户被锁定或数据库中不存在该用户，还将返回 false 值。 
        /// </returns>
        public override bool ChangePasswordQuestionAndAnswer(string username, string password,
            string newPasswordQuestion, string newPasswordAnswer)
        {
            SecUtility.CheckParameter(ref username, true, true, true, 255, "username");
            SecUtility.CheckParameter(ref password, true, true, false, 128, "password");

            string salt;
            System.Web.Security.MembershipPasswordFormat passwordFormat;

            if (!CheckPassword(username, password, false, false, out salt, out passwordFormat))
            {
                return false;
            }

            SecUtility.CheckParameter(ref newPasswordQuestion, this.RequiresQuestionAndAnswer,
                this.RequiresQuestionAndAnswer, false, 255, "newPasswordQuestion");

            if (newPasswordAnswer != null)
            {
                newPasswordAnswer = newPasswordAnswer.Trim();
            }

            SecUtility.CheckParameter(ref newPasswordAnswer, this.RequiresQuestionAndAnswer,
                this.RequiresQuestionAndAnswer, false, 128, "newPasswordAnswer");

            string answer;
            if (!String.IsNullOrEmpty(newPasswordAnswer))
            {
                answer = EncodePassword(newPasswordAnswer.ToLower(
                    System.Globalization.CultureInfo.InvariantCulture), passwordFormat, salt);
            }
            else
            {
                answer = newPasswordAnswer;
            }

            SecUtility.CheckParameter(ref answer, this.requiresQuestionAndAnswer, 
                this.requiresQuestionAndAnswer, false, 128, "newPasswordAnswer");

            int status = new UserService(repository).
                ChangePasswordQuestionAndAnswer(
                this.applicationName, 
                username, 
                newPasswordQuestion, 
                answer);

            if (status != 0)
            {
                throw new ProviderException(GetExceptionText(status));
            }

            return true;
        }

        /// <summary>
        /// 向成员资格数据库添加一个新用户。
        /// </summary>
        /// <param name="username">新用户的用户名。</param>
        /// <param name="password">新用户的密码。</param>
        /// <param name="email">新用户的电子邮件地址。</param>
        /// <param name="passwordQuestion">新用户的密码提示问题。</param>
        /// <param name="passwordAnswer">新用户的密码提示问题答案。</param>
        /// <param name="isApproved">是否允许验证新用户。</param>
        /// <param name="providerUserKey">唯一标识数据库中成员资格用户的 Guid。 </param>
        /// <param name="status">一个 MembershipCreateStatus 值，指示是否成功创建用户。 </param>
        /// <returns>用于新创建用户的 MembershipUser 对象。 如果没有创建用户，此方法将返回 null。 </returns>
        public override System.Web.Security.MembershipUser CreateUser(string username, 
            string password, string email,
            string passwordQuestion, string passwordAnswer, 
            bool isApproved, object providerUserKey,
            out System.Web.Security.MembershipCreateStatus status)
        {
            if (!SecUtility.ValidateParameter(ref password, true, true, false, 128))
            {
                status = System.Web.Security.MembershipCreateStatus.InvalidPassword;
                return null;
            }

            string salt = GenerateSalt();
            string pass = EncodePassword(password, this.passwordFormat, salt);

            if (pass.Length > 128)
            {
                status = System.Web.Security.MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if (passwordAnswer != null)
            {
                passwordAnswer = passwordAnswer.Trim();
            }

            string answer;
            if (!String.IsNullOrEmpty(passwordAnswer))
            {
                if (passwordAnswer.Length > 128)
                {
                    status = System.Web.Security.MembershipCreateStatus.InvalidAnswer;
                    return null;
                }
                answer = EncodePassword(passwordAnswer.ToLower(
                    System.Globalization.CultureInfo.InvariantCulture), this.passwordFormat, salt);
            }
            else
            {
                answer = passwordAnswer;
            }

            if (!SecUtility.ValidateParameter(ref answer, this.requiresQuestionAndAnswer, true, false, 128))
            {
                status = System.Web.Security.MembershipCreateStatus.InvalidAnswer;
                return null;
            }

            if (!SecUtility.ValidateParameter(ref username, true, true, true, 255))
            {
                status = System.Web.Security.MembershipCreateStatus.InvalidUserName;
                return null;
            }

            if (!SecUtility.ValidateParameter(ref email, this.requiresUniqueEmail, 
                this.requiresUniqueEmail, false, 255))
            {
                status = System.Web.Security.MembershipCreateStatus.InvalidEmail;
                return null;
            }

            if (!SecUtility.ValidateParameter(ref passwordQuestion, 
                this.requiresQuestionAndAnswer, true, false, 255))
            {
                status = System.Web.Security.MembershipCreateStatus.InvalidQuestion;
                return null;
            }

            if ((providerUserKey != null) && !(providerUserKey is Int32))
            {
                status = System.Web.Security.MembershipCreateStatus.InvalidProviderUserKey;
                return null;
            }


            if (password.Length < this.minRequiredPasswordLength)
            {
                status = System.Web.Security.MembershipCreateStatus.InvalidPassword;
                return null;
            }

            int nonAlphanumericCharacters = 0;

            for (int i = 0; i < password.Length; i++)
            {
                if (!char.IsLetterOrDigit(password, i))
                {
                    nonAlphanumericCharacters++;
                }
            }

            if (nonAlphanumericCharacters < this.minRequiredNonAlphanumericCharacters)
            {
                status = System.Web.Security.MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if ((this.passwordStrengthRegularExpression.Length > 0) 
                && !Regex.IsMatch(password, this.passwordStrengthRegularExpression))
            {
                status = System.Web.Security.MembershipCreateStatus.InvalidPassword;
                return null;
            }

            System.Web.Security.ValidatePasswordEventArgs args = 
                new System.Web.Security.ValidatePasswordEventArgs(username, password, true);
            this.OnValidatingPassword(args);
            if (args.Cancel)
            {
                status = System.Web.Security.MembershipCreateStatus.InvalidPassword;
                return null;
            }

            DateTime currentTimeUtc = this.RoundToSeconds(DateTime.UtcNow);

            int userID;

            status = (System.Web.Security.MembershipCreateStatus)CreateUser(
                this.applicationName, 
                username, 
                pass, 
                salt, 
                email,
                passwordQuestion, 
                answer, 
                isApproved,
                this.requiresUniqueEmail, 
                this.passwordFormat, 
                currentTimeUtc, 
                out userID);

            if (status != 0)
            {
                return null;
            }

            currentTimeUtc = currentTimeUtc.ToLocalTime();
            return new System.Web.Security.MembershipUser(this.Name, username, userID, email, 
                passwordQuestion, null, isApproved, false, currentTimeUtc, currentTimeUtc, 
                currentTimeUtc, currentTimeUtc, new DateTime(1754, 1, 1));

        }

        /// <summary>
        /// 从成员资格数据库删除用户的成员资格信息。
        /// </summary>
        /// <param name="username">要删除的用户的名称。</param>
        /// <param name="deleteAllRelatedData">
        /// 如果为 true，则从数据库中删除与该用户相关的数据；
        /// 如果为 false，则将与该用户相关的数据保留在数据库。 
        /// </param>
        /// <returns>
        /// 如果用户已删除，则为 true；否则为 false。 
        /// 如果数据库中没有此用户，也会返回 false 值。 
        /// </returns>
        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            SecUtility.CheckParameter(ref username, true, true, true, 255, "username");

            int status = new UserService(repository).DeleteUser(
                this.applicationName,
                username, 
                deleteAllRelatedData);

            if (status != 0)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// 返回成员资格用户的集合，其中用户的电子邮件地址字段包含指定的电子邮件地址。
        /// </summary>
        /// <param name="emailToMatch">要搜索的电子邮件地址。</param>
        /// <param name="pageIndex">要返回的结果页的索引。pageIndex 从零开始。 </param>
        /// <param name="pageSize">要返回的结果页的大小。</param>
        /// <param name="totalRecords">匹配用户的总数。</param>
        /// <returns>
        /// 包含一页 pageSizeMembershipUser 对象的 MembershipUserCollection，
        /// 这些对象从 pageIndex 指定的页开始。
        /// </returns>
        public override System.Web.Security.MembershipUserCollection FindUsersByEmail(
            string emailToMatch, int pageIndex,
            int pageSize, out int totalRecords)
        {
            SecUtility.CheckParameter(ref emailToMatch, false, false, false, 128, "emailToMatch");

            System.Web.Security.MembershipUserCollection membershipUsers = 
                new System.Web.Security.MembershipUserCollection();

            if (pageIndex < 0)
            {
                throw new ArgumentException("PageIndex bad.");
            }

            if (pageSize < 1)
            {
                throw new ArgumentException("PageSize bad.");
            }

            long lBound = (long)pageIndex * pageSize;
            long uBound = lBound + pageSize - 1;

            if (uBound > Int32.MaxValue)
            {
                throw new ArgumentException("PageIndex,pageSize bad.");
            }           

            IList<User> users = new UserService(repository).FindUsersByEmail(
                this.applicationName, 
                emailToMatch, 
                pageIndex,
                pageSize, 
                out totalRecords);
         

            foreach (User user in users)
            {
                System.Web.Security.MembershipUser membershipUser = 
                    new System.Web.Security.MembershipUser(this.Name, user.Username, user.Id, 
                        user.Email, user.PasswordQuestion, user.Comment, user.IsApproved, 
                        user.IsLockedOut, user.CreateDate.ToLocalTime(),
                        user.LastLoginDate.ToLocalTime(), user.LastActivityDate.ToLocalTime(),
                        user.LastPasswordChangedDate.ToLocalTime(), user.LastLockoutDate.ToLocalTime());

                membershipUsers.Add(membershipUser);
            }

            return membershipUsers;
        }

        /// <summary>
        /// 获取一个成员资格用户的集合，其中的用户名包含要匹配的指定用户名。
        /// </summary>
        /// <param name="usernameToMatch">要搜索的用户名。</param>
        /// <param name="pageIndex">要返回的结果页的索引。pageIndex 从零开始。</param>
        /// <param name="pageSize">要返回的结果页的大小。</param>
        /// <param name="totalRecords">当此方法返回时，此参数包含匹配用户的总数。</param>
        /// <returns>
        /// 包含一页 pageSizeMembershipUser 对象的 MembershipUserCollection，
        /// 这些对象从 pageIndex 指定的页开始。 
        /// </returns>
        public override System.Web.Security.MembershipUserCollection FindUsersByName(
            string usernameToMatch,
            int pageIndex, int pageSize, out int totalRecords)
        {
            SecUtility.CheckParameter(ref usernameToMatch, true, true, false, 255, "usernameToMatch");

            System.Web.Security.MembershipUserCollection membershipUsers = 
                new System.Web.Security.MembershipUserCollection();

            if (pageIndex < 0)
            {
                throw new ArgumentException("PageIndex bad.");
            }

            if (pageSize < 1)
            {
                throw new ArgumentException("PageSize bad.");
            }

            long lBound = (long)pageIndex * pageSize;
            long uBound = lBound + pageSize - 1;

            if (uBound > Int32.MaxValue)
            {
                throw new ArgumentException("PageIndex,pageSize bad.");
            }
           

            IList<User> users = new UserService(repository).FindUsersByName(
                this.applicationName, 
                usernameToMatch, 
                pageIndex,
                pageSize, 
                out totalRecords);           


            foreach (User user in users)
            {
                System.Web.Security.MembershipUser membershipUser = 
                    new System.Web.Security.MembershipUser(this.Name, user.Username, user.Id, user.Email,
                    user.PasswordQuestion, user.Comment, user.IsApproved, user.IsLockedOut, user.CreateDate.ToLocalTime(),
                    user.LastLoginDate.ToLocalTime(), user.LastActivityDate.ToLocalTime(),
                    user.LastPasswordChangedDate.ToLocalTime(), user.LastLockoutDate.ToLocalTime());
                membershipUsers.Add(membershipUser);
            }

            return membershipUsers;
        }

        /// <summary>
        /// 获取成员资格数据库中所有用户的集合。
        /// </summary>
        /// <param name="pageIndex">要返回的结果页的索引。pageIndex 从零开始。 </param>
        /// <param name="pageSize">要返回的结果页的大小。</param>
        /// <param name="totalRecords">用户的总数。</param>
        /// <returns>
        /// MembershipUser 对象的 MembershipUserCollection，
        /// 表示数据库中配置的 ApplicationName 的所有用户。
        /// </returns>
        public override System.Web.Security.MembershipUserCollection GetAllUsers(int pageIndex, int pageSize,
            out int totalRecords)
        {
            System.Web.Security.MembershipUserCollection membershipUsers =
                new System.Web.Security.MembershipUserCollection();

            if (pageIndex < 0)
            {
                throw new ArgumentException("PageIndex bad.");
            }

            if (pageSize < 1)
            {
                throw new ArgumentException("PageSize bad.");
            }

            long lBound = (long)pageIndex * pageSize;
            long uBound = lBound + pageSize - 1;

            if (uBound > Int32.MaxValue)
            {
                throw new ArgumentException("PageIndex,pageSize bad.");
            }
           

            IList<User> users = new UserService(repository).GetAllUsers(
                this.applicationName, 
                pageIndex,
                pageSize, 
                out totalRecords);

            foreach (User user in users)
            {
                System.Web.Security.MembershipUser membershipUser = 
                    new System.Web.Security.MembershipUser(this.Name, user.Username, user.Id, user.Email,
                    user.PasswordQuestion, user.Comment, user.IsApproved, user.IsLockedOut, user.CreateDate.ToLocalTime(),
                    user.LastLoginDate.ToLocalTime(), user.LastActivityDate.ToLocalTime(),
                    user.LastPasswordChangedDate.ToLocalTime(), user.LastLockoutDate.ToLocalTime());
                membershipUsers.Add(membershipUser);
            }

            return membershipUsers;
        }

        /// <summary>
        /// 返回当前访问该应用程序的用户数。
        /// </summary>
        /// <returns>当前访问该应用程序的用户数。</returns>
        public override int GetNumberOfUsersOnline()
        {
            return Convert.ToInt32(new UserService(repository).GetNumberOfUsersOnline(
                this.applicationName,
                System.Web.Security.Membership.UserIsOnlineTimeWindow, 
                DateTime.UtcNow));
        }

        /// <summary>
        /// 从成员资格数据库返回指定用户名的密码。
        /// </summary>
        /// <param name="username">为其检索密码的用户。</param>
        /// <param name="answer">用户的密码提示问题答案。</param>
        /// <returns>指定用户名所对应的密码。</returns>
        public override string GetPassword(string username, string passwordAnswer)
        {
            if (!this.enablePasswordRetrieval)
            {
                throw new NotSupportedException("Membership password retrieval not supported.");
            }

            SecUtility.CheckParameter(ref username, true, true, true, 255, "username");

            string answer = GetEncodedPasswordAnswer(username, passwordAnswer);

            SecUtility.CheckParameter(ref answer, this.requiresQuestionAndAnswer, 
                this.requiresQuestionAndAnswer, false, 128, "passwordAnswer");

            System.Web.Security.MembershipPasswordFormat passwordFormat = 
                System.Web.Security.MembershipPasswordFormat.Clear;
            int status = 0;
            string pass = new UserService(repository).GetPassword(
                this.applicationName, 
                username, 
                answer, 
                this.requiresQuestionAndAnswer,
                this.maxInvalidPasswordAttempts, 
                this.passwordAttemptWindow, 
                out passwordFormat, 
                out status, 
                DateTime.UtcNow);

            if (pass == null)
            {
                string errText = this.GetExceptionText(status);

                if (this.IsStatusDueToBadPassword(status))
                {
                    throw new System.Web.Security.MembershipPasswordException(errText);
                }

                throw new ProviderException(errText);
            }

            return UnEncodePassword(pass, passwordFormat);

        }

      

        /// <summary>
        /// 从数据源获得与指定的唯一标识符关联的成员资格用户的信息，
        /// 并更新该用户的上次活动日期/时间戳（如果指定）。
        /// </summary>
        /// <param name="providerUserKey">用户的唯一标识符。</param>
        /// <param name="userIsOnline">
        /// 如果为 true，则更新指定用户的上次活动日期/时间戳；否则为 false。 
        /// </param>
        /// <returns>
        /// 一个 MembershipUser 对象，它表示与指定的唯一标识符关联的用户。 
        /// 如果在数据库中没有找到指定的 providerUserKey 值所对应的用户，则返回 null。
        /// </returns>
        public override System.Web.Security.MembershipUser GetUser(object providerUserKey, 
            bool userIsOnline)
        {
            if (providerUserKey == null)
            {
                throw new ArgumentNullException("providerUserKey.");
            }
            if (!(providerUserKey is Int32))
            {
                throw new ArgumentException("Membership invalid provider userKey.");
            }

            User user = new UserService(repository).GetUserByUserID(
                (int)providerUserKey, 
                userIsOnline, 
                DateTime.UtcNow);

            if (user != null)
            {
                return new System.Web.Security.MembershipUser(this.Name, user.Username, user.Id, user.Email,
                    user.PasswordQuestion, user.Comment, user.IsApproved, user.IsLockedOut, 
                    user.CreateDate.ToLocalTime(), user.LastLoginDate.ToLocalTime(), 
                    user.LastActivityDate.ToLocalTime(), user.LastPasswordChangedDate.ToLocalTime(), 
                    user.LastLockoutDate.ToLocalTime());
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// 从成员资格数据库返回用户的信息，并提供更新用户的上次活动日期/时间戳的可选功能。
        /// </summary>
        /// <param name="username">要获取其信息的用户名。</param>
        /// <param name="userIsOnline">
        /// 设置为 true 则更新用户的上次活动日期/时间戳；
        /// 设置为 false 则返回用户信息而不更新用户的上次活动日期/时间戳。 
        /// </param>
        /// <returns>
        /// 一个 MembershipUser 对象，它表示指定的用户。 
        /// 如果在数据库中没有找到指定的 username 值所对应的用户，则返回 null。 
        /// </returns>
        public override System.Web.Security.MembershipUser GetUser(
            string username, bool userIsOnline)
        {
            SecUtility.CheckParameter(ref username, true, false, true, 255, "username");

            User user = new UserService(repository).GetUserByName(
                this.applicationName, 
                username,
                userIsOnline,
                DateTime.UtcNow);

            if (user != null)
            {
                return new System.Web.Security.MembershipUser(this.Name, user.Username, user.Id, user.Email,
                    user.PasswordQuestion, user.Comment, user.IsApproved, user.IsLockedOut,
                    user.CreateDate.ToLocalTime(), user.LastLoginDate.ToLocalTime(), 
                    user.LastActivityDate.ToLocalTime(), user.LastPasswordChangedDate.ToLocalTime(), 
                    user.LastLockoutDate.ToLocalTime());
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// 获取与指定的电子邮件地址关联的用户名。
        /// </summary>
        /// <param name="email">要搜索的电子邮件地址。</param>
        /// <returns>与指定的电子邮件地址关联的用户名。如果未找到匹配项，此方法将返回 null。 </returns>
        public override string GetUserNameByEmail(string email)
        {
            SecUtility.CheckParameter(ref email, false, false, false, 128, "email");

            IList<User> users = new UserService(repository).GetUsersByEmail(
                this.applicationName, 
                email);

            if (this.requiresUniqueEmail && users.Count > 0)
            {
                throw new ProviderException("Membership more than one_user with email.");

            }

            if (users.Count == 0)
            {
                return null;
            }

            return users[0].Username;
        }

        /// <summary>
        /// 将用户密码重置为一个自动生成的新密码。
        /// </summary>
        /// <param name="username">为其重置密码的用户。</param>
        /// <param name="answer">指定的用户的密码提示问题答案。</param>
        /// <returns>指定的用户的新密码。</returns>
        public override string ResetPassword(string username, string passwordAnswer)
        {
            if (!this.EnablePasswordReset)
            {
                throw new NotSupportedException("Not configured to support password resets.");
            }

            SecUtility.CheckParameter(ref username, true, true, true, 255, "username");

            int status;
            string password;
            System.Web.Security.MembershipPasswordFormat passwordFormat;
            string salt;
            int failedPasswordAttemptCount;
            int failedPasswordAnswerAttemptCount;
            bool isApproved;
            DateTime lastLoginDate;
            DateTime lastActivityDate;

            GetPasswordWithFormat(this.applicationName, username, false, out status, 
                out password, out passwordFormat,out salt, out failedPasswordAttemptCount, 
                out failedPasswordAnswerAttemptCount, out isApproved,
                out lastLoginDate, out lastActivityDate);

            if (status == 0)
            {
                if (passwordAnswer != null)
                {
                    passwordAnswer = passwordAnswer.Trim();
                }

                string answer;

                if (!String.IsNullOrEmpty(passwordAnswer))
                {
                    answer = EncodePassword(passwordAnswer.ToLower(
                        System.Globalization.CultureInfo.InvariantCulture), passwordFormat, salt);
                }
                else
                {
                    answer = passwordAnswer;
                }

                SecUtility.CheckParameter(ref answer, this.requiresQuestionAndAnswer, 
                    this.requiresQuestionAndAnswer, false, 128, "passwordAnswer");

                string newPassword = GeneratePassword();

                System.Web.Security.ValidatePasswordEventArgs args = 
                    new System.Web.Security.ValidatePasswordEventArgs(username, newPassword, false);
                this.OnValidatingPassword(args);

                if (args.Cancel)
                {
                    if (args.FailureInformation != null)
                    {
                        throw args.FailureInformation;
                    }

                    throw new ProviderException("Membership custom password validation failure.");
                }

                status = new UserService(repository).ResetPassword(
                    this.applicationName, 
                    username, 
                    newPassword,
                    this.maxInvalidPasswordAttempts,
                    this.passwordAttemptWindow, 
                    salt,
                    passwordFormat, 
                    answer,
                    DateTime.UtcNow);

                if (status != 0)
                {
                    string errText = this.GetExceptionText(status);

                    if (this.IsStatusDueToBadPassword(status))
                    {
                        throw new System.Web.Security.MembershipPasswordException(errText);
                    }

                    throw new ProviderException(errText);
                }

            }

            if (IsStatusDueToBadPassword(status))
            {
                throw new System.Web.Security.MembershipPasswordException(GetExceptionText(status));
            }

            throw new ProviderException(this.GetExceptionText(status));
        }

        /// <summary>
        /// 清除用户的锁定状态，以便可以验证该成员资格用户。
        /// </summary>
        /// <param name="username">要为其清除锁定状态的成员资格用户的用户名。</param>
        /// <returns>
        /// 如果成功取消成员资格用户的锁定，则为 true；否则为 false。 
        /// 如果数据库中没有此用户，也会返回 false 值。
        /// </returns>
        public override bool UnlockUser(string username)
        {
            SecUtility.CheckParameter(ref username, true, true, true, 255, "username");

            int status = new UserService(repository).UnlockUser(
                this.applicationName, 
                username);

            if (status == 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 更新成员资格数据库中用户的信息。
        /// </summary>
        /// <param name="user"></param>
        public override void UpdateUser(System.Web.Security.MembershipUser user)
        {
            if (user == null)
            {
                throw new ArgumentNullException("user.");
            }

            string email = user.Email;
            SecUtility.CheckParameter(ref email, this.requiresUniqueEmail, 
                this.requiresUniqueEmail, false, 255, "email");
            user.Email = email;

            int status = new UserService(repository).UpdateUser(
                this.applicationName,
                user.UserName, 
                user.Email,
                user.Comment, 
                user.IsApproved, 
                user.LastLoginDate.ToUniversalTime(), 
                user.LastActivityDate.ToUniversalTime(),
                this.requiresUniqueEmail);

            if (status != 0)
            {
                throw new ProviderException(GetExceptionText(status));
            }
        }

        /// <summary>
        /// 验证成员资格数据库中是否存在指定的用户名和密码。
        /// </summary>
        /// <param name="username">要验证的用户的名称。</param>
        /// <param name="password">指定的用户的密码。</param>
        /// <returns>
        /// 如果指定的用户名和密码有效，则为 true；否则为 false。 
        /// 如果数据库中没有此用户，也会返回 false 值。
        /// </returns>
        public override bool ValidateUser(string username, string password)
        {
            if ((SecUtility.ValidateParameter(ref username, true, true, true, 255) &&
                SecUtility.ValidateParameter(ref password, true, true, false, 128)) &&
                CheckPassword(username, password, true, true))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

     

        private bool IsStatusDueToBadPassword(int status)
        {
            if ((status >= 2) && (status <= 6))
            {
                return true;
            }
            return (status == 99);
        }

        private string GenerateSalt()
        {
            byte[] buffer = new byte[16];
            new System.Security.Cryptography.RNGCryptoServiceProvider().GetBytes(buffer);
            return Convert.ToBase64String(buffer);
        }

        private DateTime RoundToSeconds(DateTime dt)
        {
            return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second);
        }

        private string GetEncodedPasswordAnswer(string username, string passwordAnswer)
        {
            if (passwordAnswer != null)
            {
                passwordAnswer = passwordAnswer.Trim();
            }

            if (!String.IsNullOrEmpty(passwordAnswer))
            {
                int status;
                string password;
                System.Web.Security.MembershipPasswordFormat passwordFormat;
                string salt;
                int failedPasswordAttemptCount;
                int failedPasswordAnswerAttemptCount;
                bool isApproved;
                DateTime lastLoginDate;
                DateTime lastActivityDate;

                GetPasswordWithFormat(this.applicationName, username, false, out status, out password,
                    out passwordFormat, out salt, out failedPasswordAttemptCount, out failedPasswordAnswerAttemptCount,
                    out isApproved, out lastLoginDate, out lastActivityDate);

                if (status != 0)
                {
                    throw new ProviderException(GetExceptionText(status));
                }

                return EncodePassword(passwordAnswer.ToLower(
                    System.Globalization.CultureInfo.InvariantCulture), passwordFormat, salt);
            }

            return passwordAnswer;
        }

        private string GeneratePassword()
        {
            return System.Web.Security.Membership.GeneratePassword(
                (this.minRequiredPasswordLength < 14) ? 14 : this.minRequiredPasswordLength,
                this.minRequiredNonAlphanumericCharacters);
        }


        private string EncodePassword(string pass, 
            System.Web.Security.MembershipPasswordFormat passwordFormat, string salt)
        {
            if (passwordFormat == System.Web.Security.MembershipPasswordFormat.Clear)
            {
                return pass;
            }

            byte[] bIn = System.Text.Encoding.Unicode.GetBytes(pass);
            byte[] bSalt = Convert.FromBase64String(salt);
            byte[] bRet = null;

            if (passwordFormat == System.Web.Security.MembershipPasswordFormat.Hashed)
            {
                System.Security.Cryptography.HashAlgorithm hashAlgorithm = this.GetHashAlgorithm();
                if (hashAlgorithm is System.Security.Cryptography.KeyedHashAlgorithm)
                {
                    System.Security.Cryptography.KeyedHashAlgorithm keyedHashAlgorithm = 
                        (System.Security.Cryptography.KeyedHashAlgorithm)hashAlgorithm;
                    if (keyedHashAlgorithm.Key.Length == bSalt.Length)
                    {
                        keyedHashAlgorithm.Key = bSalt;
                    }
                    else
                    {
                        if (keyedHashAlgorithm.Key.Length < bSalt.Length)
                        {
                            byte[] bKey = new byte[keyedHashAlgorithm.Key.Length];
                            Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
                            keyedHashAlgorithm.Key = bKey;
                        }
                        else
                        {
                            byte[] bKey = new byte[keyedHashAlgorithm.Key.Length];
                            int num;
                            for (int i = 0; i < bKey.Length; i += num)
                            {
                                num = Math.Min(bSalt.Length, bKey.Length - i);
                                Buffer.BlockCopy(bSalt, 0, bKey, i, num);
                            }
                            keyedHashAlgorithm.Key = bKey;
                        }
                    }
                    bRet = keyedHashAlgorithm.ComputeHash(bIn);
                }
                else
                {
                    byte[] bAll = new byte[bSalt.Length + bIn.Length];
                    Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
                    Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
                    bRet = hashAlgorithm.ComputeHash(bAll);
                }
            }

            else //System.Web.Security.MembershipPasswordFormat.Encrypted
            {
                byte[] bAll = new byte[bSalt.Length + bIn.Length];
                Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
                Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
                bRet = this.EncryptPassword(bAll);
            }

            return Convert.ToBase64String(bRet);
        }

        private System.Security.Cryptography.HashAlgorithm GetHashAlgorithm()
        {
            if (this.s_HashAlgorithm != null)
            {
                return System.Security.Cryptography.HashAlgorithm.Create(this.s_HashAlgorithm);
            }
            string type = System.Web.Security.Membership.HashAlgorithmType;
           
            System.Security.Cryptography.HashAlgorithm hashAlgorithm = 
                System.Security.Cryptography.HashAlgorithm.Create(type);
            if (hashAlgorithm == null)
            {
                throw new ProviderException("Could not create a hash algorithm");
            }
            this.s_HashAlgorithm = type;
            return hashAlgorithm;
        }


        private string UnEncodePassword(string pass, 
            System.Web.Security.MembershipPasswordFormat passwordFormat)
        {
            switch (passwordFormat)
            {
                case System.Web.Security.MembershipPasswordFormat.Clear:
                    return pass;

                case System.Web.Security.MembershipPasswordFormat.Hashed:
                    throw new ProviderException("Provider can not decode hashed password");
            }

            byte[] buffer = this.DecryptPassword(Convert.FromBase64String(pass));

            if (buffer == null)
            {
                return null;
            }

            return System.Text.Encoding.Unicode.GetString(buffer, 16, buffer.Length - 16);
        }

        private bool CheckPassword(string username, string password, bool updateLastLoginActivityDate, 
            bool failIfNotApproved)
        {
            string salt;
            System.Web.Security.MembershipPasswordFormat passwordFormat;
            return CheckPassword(username, password, updateLastLoginActivityDate, failIfNotApproved,
                out salt, out passwordFormat);
        }

        private bool CheckPassword(string username, string password, bool updateLastLoginActivityDate, bool failIfNotApproved,
            out string salt, out System.Web.Security.MembershipPasswordFormat passwordFormat)
        {
            int status;
            string pass;
            int failedPasswordAttemptCount;
            int failedPasswordAnswerAttemptCount;
            bool isApproved;
            DateTime lastLoginDate;
            DateTime lastActivityDate;


            GetPasswordWithFormat(this.applicationName, username, updateLastLoginActivityDate, out status,
                out pass, out passwordFormat, out salt, out failedPasswordAttemptCount,
                out failedPasswordAnswerAttemptCount, out isApproved, out lastLoginDate, out  lastActivityDate);

            if (status != 0)
            {
                return false;
            }
            if (!isApproved && failIfNotApproved)
            {
                return false;
            }

            password = EncodePassword(password, passwordFormat, salt);

            bool isPasswordCorrect = password.Equals(pass);

            if ((isPasswordCorrect && (failedPasswordAttemptCount == 0)) && (failedPasswordAnswerAttemptCount == 0))
            {
                return true;
            }

            new UserService(repository).UpdateUserInfo(this.applicationName, username, isPasswordCorrect,
                this.passwordAttemptWindow, this.maxInvalidPasswordAttempts,
                updateLastLoginActivityDate, DateTime.UtcNow);

            return isPasswordCorrect;

        }

        private string GetExceptionText(int status)
        {
            string errText;
            switch (status)
            {
                case 0:
                    return String.Empty;

                case 1:
                    errText = "Membership user not found.";
                    break;

                case 2:
                    errText = "Membership wrong password.";
                    break;

                case 3:
                    errText = "Membership wrong answer.";
                    break;

                case 4:
                    errText = "Membership invalid password.";
                    break;

                case 5:
                    errText = "Membership invalid question.";
                    break;

                case 6:
                    errText = "Membership invalid answer.";
                    break;

                case 7:
                    errText = "Membership invalid email.";
                    break;

                case 99:
                    errText = "Membership account lock out.";
                    break;

                default:
                    errText = "Provider error.";
                    break;
            }
            return errText;
        }

        private int CreateUser(string applicationName, string username, string password, string passwordSalt,
            string email, string passwordQuestion, string passwordAnswer, bool isApproved, bool uniqueEmail,
            System.Web.Security.MembershipPasswordFormat passwordFormat, DateTime currentTimeUtc, out int userID)
        {
            userID = -1;


            if (new UserService(repository).GetUserByName(applicationName, username) != null)
            {
                return 6; //MembershipCreateStatus.DuplicateUserName
            }

            if (uniqueEmail && new UserService(repository).GetUsersByEmail(applicationName, email).Count > 0)
            {
                return 7; //MembershipCreateStatus.DuplicateEmail
            }

            User user = new User();
            user.Username = username;
            user.Email = email;
            user.Password = password;
            user.PasswordFormat = passwordFormat;
            user.PasswordSalt = passwordSalt;
            user.PasswordQuestion = passwordQuestion;
            user.PasswordAnswer = passwordAnswer;
            user.IsAnonymous = false;
            user.IsApproved = isApproved;
            user.IsLockedOut = false;
            user.CreateDate = currentTimeUtc;
            user.LastActivityDate = currentTimeUtc;
            user.LastLoginDate = currentTimeUtc;
            user.LastPasswordChangedDate = currentTimeUtc;
            user.LastLockoutDate = new DateTime(1754, 1, 1);
            user.FailedPasswordAttemptCount = 0;
            user.FailedPasswordAttemptWindowStart = new DateTime(1754, 1, 1);
            user.FailedPasswordAnswerAttemptCount = 0;
            user.FailedPasswordAnswerAttemptWindowStart = new DateTime(1754, 1, 1);
            user.Application = new ApplicationService(repository).GetApplication(applicationName);

            new UserService(repository).CreateUser(user);

            if (user.Id > 0)
            {
                userID = user.Id;
                return 0;//MembershipCreateStatus.Success
            }
            else
            {
                return 11;//MembershipCreateStatus.ProviderError
            }
        }


        private void GetPasswordWithFormat(string applicationName, string username, bool updateLastLoginActivityDate, out int status,
             out string password, out System.Web.Security.MembershipPasswordFormat passwordFormat, 
            out string passwordSalt,
             out int failedPasswordAttemptCount, out int failedPasswordAnswerAttemptCount, out bool isApproved,
             out DateTime lastLoginDate, out DateTime lastActivityDate)
        {
            User user = new UserService(repository).GetUserByName(applicationName, username);

            if (user != null)
            {
                if (updateLastLoginActivityDate && user.IsApproved)
                {
                    user.LastActivityDate = DateTime.UtcNow;
                    user.LastLoginDate = DateTime.UtcNow;

                    new UserService(repository).UpdateUser(user);
                }

                password = user.Password;
                passwordFormat = user.PasswordFormat;
                passwordSalt = user.PasswordSalt;
                failedPasswordAttemptCount = user.FailedPasswordAttemptCount;
                failedPasswordAnswerAttemptCount = user.FailedPasswordAnswerAttemptCount;
                isApproved = user.IsApproved;
                lastLoginDate = user.LastLoginDate;
                lastActivityDate = user.LastActivityDate;

                status = 0;
            }
            else
            {
                password = null;
                passwordFormat = System.Web.Security.MembershipPasswordFormat.Clear;
                passwordSalt = null;
                failedPasswordAttemptCount = 0;
                failedPasswordAnswerAttemptCount = 0;
                isApproved = false;
                lastLoginDate = DateTime.UtcNow;
                lastActivityDate = DateTime.UtcNow;

                status = 1;
            }
        }
    }
}